package com.boge.parse.web;

import com.boge.decoder.AES;
import com.boge.entity.GameOutput;
import com.boge.parse.GameEnum;
import com.boge.parse.base.BaseProtocolEncoder;
import com.boge.socket.ActionData;
import com.boge.socket.ActionDispatcher;
import com.boge.util.ZlibUtil;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.AttributeKey;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolEncoderOutput;
import org.slf4j.LoggerFactory;

import java.util.Arrays;

/**
 * @author boge
 */
public class WebSocketEncoder extends BaseProtocolEncoder {

    private final AttributeKey CONTEXT = new AttributeKey(getClass(), "context");
    /**
     * 是否是服务器
     */
    private boolean isServer;

    public WebSocketEncoder(boolean isServer) {
        this.isServer = isServer;
    }

    @Override
    public void encode(IoSession session, Object message, ProtocolEncoderOutput out) throws Exception {
        GameOutput gameOutput;
//        System.out.println("encode=" + message);
        boolean isRemoteWebSocket = session.containsAttribute(WebSocketUtils.SessionAttribute)
                && ((Boolean) session.getAttribute(WebSocketUtils.SessionAttribute));
        if (message instanceof ActionData<?>) {
            gameOutput = new GameOutput();
            ActionData<?> ad = (ActionData<?>) message;
            gameOutput.writeLong(System.currentTimeMillis());// 发送当前服务器的时间
            gameOutput.writeInt(ad.getAction());
            if (session.containsAttribute(GameEnum.SERVLET_ID)) {
                gameOutput.writeLong(ad.getSessionId());
            }
            byte[] buf = ad.getBuf();
            buf = buf == null ? new byte[0] : buf;
            gameOutput.writeInt(buf.length);
            gameOutput.write(buf, 0, buf.length);
            byte[] bytes = gameOutput.toByteArray();
            // 加密
            LoggerFactory.getLogger(getClass()).debug("加密前数据大小 bytes=" + bytes.length);
            String str = getAes(session, CONTEXT).encrypt(bytes);
            gameOutput.reset();
            bytes = str.getBytes();
            // 压缩
            bytes = ZlibUtil.compress(bytes);
            LoggerFactory.getLogger(getClass()).debug("压缩后数据大小 bytes=" + bytes.length);
            if (!isRemoteWebSocket) {
                ((ActionDispatcher) session.getHandler()).notSendData(message);
                return;
            }
            message = WebSocketCodecPacket.buildPacket(IoBuffer.wrap(bytes));
        } else if (message instanceof String) {
            message = WebSocketCodecPacket.buildPacket(IoBuffer.wrap(((String) message).getBytes()));
        }
        boolean isHandshakeResponse = message instanceof WebSocketHandShakeResponse;
        boolean isDataFramePacket = message instanceof WebSocketCodecPacket;
        IoBuffer resultBuffer;
        if (isHandshakeResponse) {// 服务器握手的时候会在这里发送
            WebSocketHandShakeResponse response = (WebSocketHandShakeResponse) message;
            resultBuffer = buildWSResponseBuffer(response);
//            System.out.println("发送1=" + resultBuffer.limit());
            out.write(resultBuffer);
        } else if (isDataFramePacket) { // 客户端只会发送这个数据
            WebSocketCodecPacket packet = (WebSocketCodecPacket) message;
            resultBuffer = isRemoteWebSocket ? buildWSDataFrameBuffer(packet.getPacket()) : packet.getPacket();
//			resultBuffer = isRemoteWebSocket ? encode(packet.getPacket()) : packet.getPacket();
//            System.out.println("发送2=" + resultBuffer.limit() + ", isRemoteWebSocket=" + isRemoteWebSocket);
            out.write(resultBuffer);
        } else {
            throw new Exception("消息不是WebSocket型");
        }

    }

    // Web Socket handshake response go as a plain string.
    private IoBuffer buildWSResponseBuffer(WebSocketHandShakeResponse response) {
        IoBuffer buffer = IoBuffer.allocate(response.getResponse().getBytes().length, false);
        buffer.setAutoExpand(true);
        buffer.put(response.getResponse().getBytes());
        buffer.flip();
        return buffer;
    }

    // Encode the in buffer according to the Section 5.2. RFC 6455
    private IoBuffer buildWSDataFrameBuffer(IoBuffer buf) {
        int isMask = isServer ? 0 : 4; // 如果是客户端  还要在加4个字节的掩码
        IoBuffer buffer;
        if (buf.limit() <= 125) {
            buffer = IoBuffer.allocate(buf.limit() + 2 + isMask, false);
            buffer.put((byte) 0x82);
            buffer.put((byte) buf.limit());
        } else if (buf.limit() <= 0xFFFF) { // 65535
            buffer = IoBuffer.allocate(buf.limit() + 4 + isMask, false);
            buffer.put((byte) 0x82);
            buffer.put((byte) 126);
            buffer.put(intTo2Bytes(buf.limit()));
        } else {
            buffer = IoBuffer.allocate(buf.limit() + 10 + isMask, false);
            buffer.put((byte) 0x82);
            buffer.put((byte) 127);
            buffer.put(intTo8Bytes(buf.limit()));
        }

        if (!isServer) { // 如果是客户端  需要添加掩码
            // 掩码  服务器发送客户端没有掩码  客户端发送服务器有掩码
            int len = buf.array().length;
            for (int i = 0; i < len; i++) {
                byte maskedByte = buf.array()[i];
                buf.array()[i] = (byte) (maskedByte ^ 1); // 掩码全是1
            }
            buffer.put((byte) 1);
            buffer.put((byte) 1);
            buffer.put((byte) 1);
            buffer.put((byte) 1);
        }

        buffer.put(buf);
        buffer.flip();
        return buffer;
    }

    /**
     * 只取低16位（因为java没有无符号类型）
     * @param value
     * @return
     */
    public static byte[] intTo2Bytes(int value) {
        byte[] src = new byte[2];
        src[0] =  (byte) ((value>>8) & 0xFF);
        src[1] =  (byte) (value & 0xFF);
        return src;
    }

    /**
     * 高32位置0，也就是说最大长度为2的32次方-1(大约512MBytes数据量)
     * @param value
     * @return
     */
    public static byte[] intTo8Bytes(int value) {
        byte[] src = new byte[8];
        src[0] =  (byte) (0x00);
        src[1] =  (byte) (0x00);
        src[2] =  (byte) (0x00);
        src[3] =  (byte) (0x00);
        src[4] =  (byte) ((value>>24) & 0xFF);
        src[5] =  (byte) ((value>>16) & 0xFF);
        src[6] =  (byte) ((value>>8) & 0xFF);
        src[7] =  (byte) (value & 0xFF);
        return src;
    }

    @Override
    public void dispose(IoSession session) throws Exception {
        AES aes = (AES) session.getAttribute(CONTEXT);
        if (aes != null) {
            session.removeAttribute(CONTEXT);
        }
    }

}
